package AbstractMapper;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeSet;

import org.apache.commons.collections.CollectionUtils;
import org.opencyc.cycobject.CycConstant;
import org.opencyc.cycobject.CycList;

public class DirectStructureMapper {
	/** Abstract Structure Mapping - using a systematicity-like heuristic method to perform mappings between two analogs
	 * 
	pre. If there are less than two elements on either analog, expand by getting the 1st level ISAS and GENLS
	a. Create a definition of internal structure: Best Pair for each element within analog. (using currently selected sorting method)	  
	b. Create mappings for the elements that are linked more than once within their analogs.
	c. For every element mapped create a new map by linking the elements' most similar pairs to each other.
	d. Map as many remaining elements using the similarity rankings of their internal structures.
	e. Map until one of the analogs is fully mapped. (make all pair mappings and discard the lowest pair)
	 * @param abstractAnalogyManager TODO
	 * @param analog1 TODO
	 * @param analog2 TODO
	 *
	 */
	@SuppressWarnings("unchecked")
	protected static AbstractAnalogy createMappingsBasedOnSimpleStructure(AbstractAnalogyManager abstractAnalogyManager, CycList analog1, CycList analog2)
	{
		ArrayList<AbstractElementPair> mappings = new ArrayList<AbstractElementPair>();//store mappings here
		AbstractElementPair newPair;
		CycList mapped1Elements = new CycList(); //to maintain 1-to-1 mappings, check against these lists 
		CycList mapped2Elements = new CycList();
		int smallerIndex;
		
		abstractAnalogyManager.expl+="\nUser Input: \n";
		abstractAnalogyManager.expl+="Analog 1: "+analog1 + "\n";
		abstractAnalogyManager.expl+="Analog 2: "+analog2 + "\n\n";
	
		//start of pre
		int oldExpandSetting = abstractAnalogyManager.cycBridge.getExpandBy();
		if ((abstractAnalogyManager.cycBridge.getExpandBy()>0) || ((analog1.size()<2) && (analog2.size()<2))) 
		{
			if (abstractAnalogyManager.cycBridge.getExpandBy()==0)
			{
				abstractAnalogyManager.expl+="Analogs too small! Forced expansion...\n";
				abstractAnalogyManager.cycBridge.setExpandBy(1);
			}
			CycList oldA1 = new CycList (analog1);
			CycList oldA2 = new CycList (analog2);
			boolean expanded = abstractAnalogyManager.expandConceptsInAnalogs(analog1,analog2);
			while ((analog1.size()<2) && (analog2.size()<2))
			{
				analog1 = new CycList(oldA1);
				analog2 = new CycList(oldA2);
				if (abstractAnalogyManager.cycBridge.setExpandBy(abstractAnalogyManager.cycBridge.getExpandBy()+1)>=5) return null;//return null if expansion failed
				expanded = abstractAnalogyManager.expandConceptsInAnalogs(analog1,analog2);
			}
			if (expanded)
			{
				
				abstractAnalogyManager.expl+="* Preparation - Expanded Analogs: \n";
				abstractAnalogyManager.expl+="Analog 1: "+analog1 + "\n";
				abstractAnalogyManager.expl+="Analog 2: "+analog2 + "\n\n";		
			}
			
			abstractAnalogyManager.cycBridge.setExpandBy(0);//so other createMapping methods don't expand as well.
			//else expansion failed
		}//end of pre
		
		//start of step a. Create a definition of internal structure: Best Pair for each element within analog. (using currently selected sorting method)
		
		ArrayList<AbstractAnalogy> a1pairs = abstractAnalogyManager.createAllPairMappings(analog1);
		ArrayList<AbstractAnalogy> a2pairs = abstractAnalogyManager.createAllPairMappings(analog2);
		
		if ((a1pairs==null) || (a2pairs==null))	return null;//throw exception if building pairs failed
		
		abstractAnalogyManager.calcScores(a1pairs);
		abstractAnalogyManager.calcScores(a2pairs);
		
		ArrayList<AbstractAnalogy> sortedStruct1 = abstractAnalogyManager.sortMappings(a1pairs);
		ArrayList<AbstractAnalogy> sortedStruct2 = abstractAnalogyManager.sortMappings(a2pairs);
		
		CycList analog1Copy = new CycList(analog1);
		CycList analog2Copy = new CycList(analog2);
		
		CycList analog1Tally = new CycList(); //for step b
		CycList analog2Tally = new CycList(); //for step b
		
		ArrayList<AbstractElementPair> bestPairsInternal1 = new ArrayList<AbstractElementPair>();
		ArrayList<AbstractElementPair> bestPairsInternal2 = new ArrayList<AbstractElementPair>();
		
		//check each element off against the list of constants and create a best pair internal list		
		//analog1Copy
		for (int i =0 ; i < sortedStruct1.size(); i++)	
		{
			ArrayList<AbstractElementPair> pair = sortedStruct1.get(i).getMaps();
			for (int j = 0; j < pair.size(); j++)
			{
				if ((!pair.get(j).getConstOne().equals(pair.get(j).getConstTwo()))//don't accept pairs with duplicate constants or duplicate pairs
					&& (!bestPairsInternal1.contains(pair.get(j))))
				{
					if (analog1Copy.remove(pair.get(j).getConstOne()))
					{
						//Log.current.println("Adding pair to best pairs int1: "+ pair.get(j));
						bestPairsInternal1.add(pair.get(j));
						analog1Copy.remove(pair.get(j).getConstTwo());
						
						//tally how many times each element is in the best pairs list (because internal mappings are not 1-to-1)
						analog1Tally.add(pair.get(j).getConstOne());
						analog1Tally.add(pair.get(j).getConstTwo());
						
					}
					else if (analog1Copy.remove(pair.get(j).getConstTwo()))
					{
						//Log.current.println("Adding pair to best pairs int1: "+ pair.get(j));
						bestPairsInternal1.add(pair.get(j));
						
						//tally for part b
						analog1Tally.add(pair.get(j).getConstOne());
						analog1Tally.add(pair.get(j).getConstTwo());
					}
				}
			}
		}
		//now analog2Copy		
		for (int i =0 ; i < sortedStruct2.size(); i++)	
		{
			ArrayList<AbstractElementPair> pair = sortedStruct2.get(i).getMaps();
			for (int j = 0; j < pair.size(); j++)
			{
				if ((!pair.get(j).getConstOne().equals(pair.get(j).getConstTwo()))//don't accept pairs with duplicate constants or duplicate pairs
					&& (!bestPairsInternal2.contains(pair.get(j))))
				{
					if (analog2Copy.remove(pair.get(j).getConstOne()))
					{
						//Log.current.println("Adding pair to best pairs int2: "+ pair.get(j));
						bestPairsInternal2.add(pair.get(j));
						analog2Copy.remove(pair.get(j).getConstTwo());
						
						//tally how many times each element is in the best pairs list (because internal mappings are not 1-to-1)
						analog2Tally.add(pair.get(j).getConstOne());
						analog2Tally.add(pair.get(j).getConstTwo());
					}
					else if (analog2Copy.remove(pair.get(j).getConstTwo()))
					{
						//Log.current.println("Adding pair to best pairs int2: "+ pair.get(j));						
						bestPairsInternal2.add(pair.get(j));
					
						//tally for part b
						analog2Tally.add(pair.get(j).getConstOne());
						analog2Tally.add(pair.get(j).getConstTwo());
					}
				}
			}
		}//step a done, internal structure created from most similar pairs within analogs.		
		
		//start of step b. Create mappings for the elements that are linked more than once within their internal structure mappings.				
						
		int analogSize = analog1.size();	
		Integer[] elements1Tally = new Integer[analogSize];
		analogSize = analog2.size();
		Integer[] elements2Tally = new Integer[analogSize];
		
		for (int i = 0; i < analog1.size(); i++)		
			elements1Tally[i] = CollectionUtils.cardinality( analog1.get(i), analog1Tally );
		
		for (int i = 0; i < analog2.size(); i++)		
			elements2Tally[i] = CollectionUtils.cardinality( analog2.get(i), analog2Tally );
		
		SortableResult current;
		ArrayList<SortableResult> sort1Results = new ArrayList<SortableResult>();		
		ArrayList<SortableResult> sort2Results = new ArrayList<SortableResult>();
		for (int i = 0; i < elements1Tally.length; i++)
		{
			if (elements1Tally[i]>1)			
			{
				current = new SortableResult(elements1Tally[i],analog1.get(i));
				sort1Results.add(current);
			}
		}
		for (int i = 0; i < elements2Tally.length; i++)
		{
			if (elements2Tally[i]>1)
			{
				current = new SortableResult(elements2Tally[i],analog2.get(i));
				sort2Results.add(current);
			}
		}
		//START EXPLANATION STEP A
		abstractAnalogyManager.expl+="* Step A - Create Internal Structure Mappings:\n";
		abstractAnalogyManager.expl+="Internal Structure Analog 1:\n----------------------------"+"\n";
		abstractAnalogyManager.expl+="Elements: " + analog1+"\n";
		abstractAnalogyManager.expl+="Tally   : [";
		for (int i = 0; i < elements1Tally.length; i++)
		{
			abstractAnalogyManager.expl+=elements1Tally[i];
			if (i < (elements1Tally.length-1))
				abstractAnalogyManager.expl+= ", ";
		}
		abstractAnalogyManager.expl+="]\n";
		abstractAnalogyManager.expl+="Pairs: \n";
		for (int i =0 ; i < bestPairsInternal1.size(); i++)	
		{
			abstractAnalogyManager.expl+=bestPairsInternal1.get(i)+"\n";
			//expl+="First Matches: " + bestPairsInternal1.get(i).getFirstMatches()+"\n";
		}
		
		abstractAnalogyManager.expl+="\nInternal Structure Analog 2:\n----------------------------"+"\n";
		abstractAnalogyManager.expl+="Elements: " + analog2+"\n";
		abstractAnalogyManager.expl+="Tally   : [";
		for (int i = 0; i < elements2Tally.length; i++)
		{
			abstractAnalogyManager.expl+=elements2Tally[i];
			if (i < (elements2Tally.length-1))
				abstractAnalogyManager.expl+= ", ";
		}
		abstractAnalogyManager.expl+="]\n";
		abstractAnalogyManager.expl+="Pairs: \n";
		for (int i =0 ; i < bestPairsInternal2.size(); i++)
		{
			abstractAnalogyManager.expl+=bestPairsInternal2.get(i)+"\n";
			//expl+="First Matches: " + bestPairsInternal2.get(i).getFirstMatches()+"\n";
		}
		abstractAnalogyManager.expl+="\n";
		//END OF STEP A EXPLANATION
		if ((!sort1Results.isEmpty()) && (!sort2Results.isEmpty()))
		{
			TreeSet<SortableResult> sorted1Results = new TreeSet<SortableResult>(sort1Results);
			TreeSet<SortableResult> sorted2Results = new TreeSet<SortableResult>(sort2Results);
			
			ArrayList<CycConstant> final1Results = new ArrayList<CycConstant>();
			ArrayList<CycConstant> final2Results = new ArrayList<CycConstant>();
			
			for (Iterator<SortableResult> it=sorted1Results.iterator(); it.hasNext(); )			
				final1Results.add((CycConstant)it.next().getObject());
			
			for (Iterator<SortableResult> it=sorted2Results.iterator(); it.hasNext(); )			
				final2Results.add((CycConstant)it.next().getObject());
							
			smallerIndex = final1Results.size();
			if (final2Results.size() < smallerIndex)
				smallerIndex = final2Results.size();
			for (int i = 0; i < smallerIndex; i++)
			{
				newPair = new AbstractElementPair(final1Results.get(i),final2Results.get(i),abstractAnalogyManager.cycBridge);
				mappings.add(newPair);
			
				//add these to the list so they won't be double mapped
				mapped1Elements.add((CycConstant)final1Results.get(i));
				mapped2Elements.add((CycConstant)final2Results.get(i));
			
				abstractAnalogyManager.expl+="* Step B - Create Mapping for branch nodes: "+ newPair+"\n";
			}
		}
		//step b complete
		
		//start of step c. For every element mapped create a new map by linking the elements' most similar pairs to each other.
		CycConstant corrElem1, corrElem2;
		for (int i = 0; i < mapped1Elements.size(); i++) //mapped1Elemenents and mapped2Elements are the same size
		{
			corrElem1 = DirectStructureMapper.findCorrespondingPair((CycConstant)mapped1Elements.get(i),bestPairsInternal1);
			if ((!mapped1Elements.contains(corrElem1)) && (corrElem1!=null))
			{
				corrElem2 = DirectStructureMapper.findCorrespondingPair((CycConstant)mapped2Elements.get(i),bestPairsInternal2);
				if ((!mapped2Elements.contains(corrElem2)) && (corrElem2!=null))
				{
					newPair = new AbstractElementPair(corrElem1, corrElem2, abstractAnalogyManager.cycBridge);
					mappings.add(newPair);
					mapped1Elements.add(corrElem1);
					mapped2Elements.add(corrElem2);
					abstractAnalogyManager.expl+="* Step C - Create Mapping for half-mapped pairs: "+ newPair+"\n";
				}
			}
		}//step c complete
		
		//start of step d. Map until one of the analogs is fully mapped. (make all pair mappings and discard the lowest pair)
		CycList elementsToMap1 = new CycList();
		CycList elementsToMap2 = new CycList();
		
		for (int i =0; i < analog1.size(); i ++)
		{
			if (!mapped1Elements.contains(analog1.get(i)))
				elementsToMap1.add(analog1.get(i));
		}
		for (int i =0; i < analog2.size(); i ++)
		{
			if (!mapped2Elements.contains(analog2.get(i)))
				elementsToMap2.add(analog2.get(i));			
		}
		
		elementsToMap1.addAll(elementsToMap2);
		//abstractAnalogyManager.expl+="ELEMENTS LEFT TO MAP: "+ elementsToMap1+"\n";
		ArrayList<AbstractAnalogy> lastMappings = abstractAnalogyManager.createAllPairMappings(elementsToMap1);
		
		if (lastMappings!=null)
		{
			abstractAnalogyManager.calcScores(lastMappings);		
			ArrayList<AbstractAnalogy> sortedLastMappings = abstractAnalogyManager.sortMappings(lastMappings);
			//abstractAnalogyManager.expl+="LAST POSSIBLE MAPPINGS TO MAP: "+ sortedLastMappings+"\n";
			
			ArrayList<AbstractElementPair> pairs; 
			
			
			for (int i = 0; i < sortedLastMappings.size(); i++)
			{
				pairs = sortedLastMappings.get(i).getMaps();
				if ((analog1.contains(pairs.get(0).getConstOne()))&& (!mapped1Elements.contains(pairs.get(0).getConstOne())))
				{
					if ((analog2.contains(pairs.get(0).getConstTwo()))&&(!mapped2Elements.contains(pairs.get(0).getConstTwo())))
					{
						mappings.add(pairs.get(0));
						abstractAnalogyManager.expl+="* Step D - Created Mapping for similar and high-ranked internal-structure pairs: "+ pairs.get(0)+"\n";
						
						mapped1Elements.add(pairs.get(0).getConstOne());
						mapped2Elements.add(pairs.get(0).getConstTwo());
						
					}
				}			
			}
		}
		//step d complete 
		
		//expl+="\nMappings: " + mappings+"\n";		
		abstractAnalogyManager.cycBridge.setExpandBy(oldExpandSetting);//set this back before exit
		AbstractAnalogy result = new AbstractAnalogy(abstractAnalogyManager.cycBridge, mappings);
		return result;
	}

	protected static CycConstant findCorrespondingPair(CycConstant element, ArrayList<AbstractElementPair> pairList)
	{
		CycConstant result = null;
		for (int i = 0; i < pairList.size(); i++)
		{
			if (pairList.get(i).getConstOne().equals(element))
			{
				result = pairList.get(i).getConstTwo();
				break;
			}
			else if (pairList.get(i).getConstTwo().equals(element))
			{
				result = pairList.get(i).getConstOne();
				break;
			}
		}		
		return result;
	}

}
